package cn.stylefeng.guns.modular.gridsystem.websocket;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import cn.stylefeng.guns.modular.gridsystem.utils.ColorMapUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import top.blesslp.librosa.core.Librosa;
import top.blesslp.librosa.core.Utils;
import top.blesslp.librosa.core.model.SpectrumEntity;
import top.blesslp.librosa.core.model.SpectrumEnums;

import javax.sound.sampled.AudioFormat;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@ServerEndpoint("/ws/gateway/{token}")
@Component
@Slf4j
public class WebSocketGatewayServer {

    private final Log LOG = LogFactory.get();

    private final Map<String, ByteArrayOutputStream> map = new ConcurrentHashMap<>();

    private static final ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();

//    private static MusicPlayer musicPlayer;

//    @Autowired
//    public void setMessageService(MusicPlayer musicPlayer){
//        WebSocketGatewayServer.musicPlayer = musicPlayer;
//    }

    /**
     * 发送消息
     *
     * @param session
     * @param message
     * @throws IOException
     */
    public void sendMessage(Session session, String message) throws IOException {
        if (session != null) {
            synchronized (session) {
                log.info("发送信息：{}", message.length());
                session.getBasicRemote().sendText(message);
            }
        }
    }

    /**
     * 发送信息到所有人
     *
     * @param message
     * @throws IOException
     */
    public void sendMessage(float[] message) throws IOException {
        log.info("发送信息：{}", message);
        Collection<Session> values = sessionMap.values();
        for (Session value : values) {
            String s = JSONUtil.toJsonStr(message);
            value.getBasicRemote().sendText(s);
        }
    }

    /**
     * 发送消息
     *
     * @param session
     * @param message
     * @throws IOException
     */
    public void sendBytes(Session session, byte[] message) throws IOException {
        if (session != null) {
            synchronized (session) {
                session.getBasicRemote().sendBinary(ByteBuffer.wrap(message));
            }
        }
    }

    /**
     * 获取session
     *
     * @param token
     * @return
     */
    public Session getSession(String token) {
        final Session session = sessionMap.get(token);
        return session;
    }

    @OnOpen
    public void onOpen(Session session, @PathParam("token") String token) {
        sessionMap.put(token, session);
        LOG.info("{}加入websocket", token);

    }

    @OnError
    public void OnError(@PathParam("token") String token, Throwable t) {
        LOG.info("{}直接退出,错误：{}", token, t.getMessage());
    }

    @OnClose
    public void onClose(@PathParam("token") String token) throws IOException {
        sessionMap.remove(token);
        LOG.info("{}退出", token);
    }

    @OnMessage
    public void onMessage(byte[] data, Session session) {
        System.out.println("onMessage");
    }

    @OnMessage
    public void onMessage(String message, Session session) {

        try {
            if (ObjectUtil.isEmpty(message.trim())) {
                //心跳包
                session.getBasicRemote().sendText("");
                return;
            }
            final Message entity = JSONUtil.toBean(message, Message.class);
            if (entity == null) {
                LOG.info("解析失败");
                return;
            }
            final String cmd = entity.getCmd();
            LOG.info("收到消息：from:{},to:{},cmd:{}", entity.getFrom(), entity.getTo(), entity.getCmd());
            final Session destination = getSession(entity.getTo());
            if (destination == null) {
                sendMessage(session, "OFFLINE");
                return;
            }

            switch (cmd) {
                //pc => 网关
                case "START":
                case "STOP":
                case "SELF_INSPECTION":
                case "GET_FILE":
                    //网关=>pc
                case "REPLY_STOP":
                case "REPLY_GET_FILE":
                case "REPLY_SELF_INSPECTION":
                case "REPLY":
                    sendMessage(destination, JSONUtil.toJsonStr(entity));
                    break;
                case "REPLY_START":
                    JSONObject data = JSONUtil.parseObj(entity.getData());
                    String channel = data.getStr("channel");
                    String audio = data.getStr("audio");
                    String type = data.getStr("type");
                    byte[] bytes = Base64.decode(audio);

                  //  musicPlayer.writeData(bytes);
                    //16000 * 2 * 0.5 = 16000 bytes
        /*            int cacheSize = (int) (16000 * 2 * 0.5);

                    ByteArrayOutputStream cacheOutputStream = map.get(channel);

                    int cacheStatus = 0;

                    //空通道的时候，创建缓冲区
                    if(ObjectUtil.isNull(cacheOutputStream)){
                        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(cacheSize);
                        byteArrayOutputStream.write(bytes);
                        map.put(channel,byteArrayOutputStream);
                        return;
                    }else {
                        //非空的时候，写入数据到缓冲区
                                            ByteArrayOutputStream byteArrayOutputStream = map.get(channel);
                        int size = byteArrayOutputStream.size();

                        //原始数据
                        if(size < cacheSize){
                            cacheStatus = 1;
                            byteArrayOutputStream.write(bytes);
                        }
                    }*/
                    AudioFormat wavFormat = new AudioFormat(16000, 16, 1, true, false);
                    Librosa.WaveData<float[]> waveData = Librosa.loadBytes(bytes, wavFormat);
                    SpectrumEntity feature = null;
                    Map<String, Object> map = new HashMap<>();
                    if ("1".equals(type)) { //时域图
                        feature = Librosa.getFeature(waveData.data, 16000, 16, waveData.data.length / 2, waveData.data.length / 2, SpectrumEnums.SoundWav);
                    } else if ("2".equals(type)) {//时频图
                        feature = Librosa.getFeature(waveData.data, 16000, 16, waveData.data.length / 2, waveData.data.length / 2, SpectrumEnums.FFT);
                    } else if ("3".equals(type)) {//时频域图
                        feature = Librosa.getFeature(waveData.data, 16000, 16, waveData.data.length / 2, waveData.data.length / 2, SpectrumEnums.SpectrogramRT);

                        float[][] colorData = (float[][]) feature.getData();
                        float[] floats1 = colorData[0];
                        List<Float> colors = new ArrayList<>();
                        float min = 0;
                        double max;
                        max = Utils.max(colorData);
                        for (int i = 0; i < floats1.length; i++) {
                            for (float[] colorDatum : colorData) {
                                colors.add(colorDatum[i]);
                            }
                        }

                        ArrayList<Integer> colorDatas = new ArrayList<>();
                        double[][] colorMap = ColorMapUtil.colorMap;
                        for (Float color : colors) {
                            int a = Math.min(255, (int) Math.floor((((color - min) * 255.0) / (max - min))));
                            double[] doubles = colorMap[a];
                            int R = (int) (doubles[0] * 255);
                            int G = (int) (doubles[1] * 255);
                            int B = (int) (doubles[2] * 255);
                            int A = (int) (doubles[3] * 255);
                            colorDatas.add(R);
                            colorDatas.add(G);
                            colorDatas.add(B);
                            colorDatas.add(A);
                        }
                        map.put("originalData", colors);
                        map.put("colorData", colorDatas);
                    }
                    assert feature != null;
                    Object xAxis = feature.getXAxis();
                    Object yAxis = feature.getyAxis();
                    map.put("xAxis", xAxis);
                    map.put("yAxis", yAxis);
                    map.put("type", type);
                    map.put("channel", channel);
                    map.put("audio", bytes);
                    entity.setData(map);

                    sendMessage(destination, JSONUtil.toJsonStr(entity));
                    log.info("audio.size: {}", bytes.length);
                    break;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
